////////////////////////////////////////////////////////////////////////////////
// Filename: bumpmap.fx
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
static const float SHADOW_EPSILON = 0.001f;
static const float SMAP_SIZE = 1024.0f;
static const float SMAP_DX = 1.0f / SMAP_SIZE;

matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
matrix gLightWVP; 

Texture2D shaderTextures[4];
float4 ambientColor;
float4 diffuseColor;
float4 specularColor;
float	specularPower;

float4 ambientMaterial;
float4 diffuseMaterial;
float4 specularMaterial;

float3 lightDirection;
float3 cameraPosition;

float2 waveMapOffset0;
float2 waveMapOffset1;

bool bEnableNoise;
bool bEnableNormal;

SamplerState gShadowSam
{
	Filter = MIN_MAG_MIP_POINT;
	AddressU = Clamp;
	AddressV = Clamp;
};	


///////////////////
// SAMPLE STATES //
///////////////////
SamplerState SampleType
{
    Filter = MIN_MAG_MIP_LINEAR;
    AddressU = Wrap;
    AddressV = Wrap;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
	float3 normal : NORMAL;
	float3 tangent : TANGENT;
	float3 binormal : BINORMAL;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
    float2 tex1 : TEXCOORD1;
    float4 tex2 : TEXCOORD2;
    float3 viewDirection : TEXCOORD3;
   	float3 normal : NORMAL;
   	float3 tangent : TANGENT;
    float3 binormal : BINORMAL;
    float4 projTexC : TEXCOORD4;
};

BlendState AlphaBlendingOn
{
    BlendEnable[0] = TRUE;
    DestBlend = INV_SRC_ALPHA;
    SrcBlend = SRC_ALPHA;
};
////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType BumpMapVertexShader(VertexInputType input)
{
    PixelInputType output;
    
    
	// Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

	// Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.viewDirection = cameraPosition.xyz - output.position.xyz;
    output.viewDirection = normalize(output.viewDirection);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    output.projTexC = mul(input.position, gLightWVP);
    
	// Store the texture coordinates for the pixel shader.
    output.tex = input.tex + waveMapOffset0;
    output.tex1 = input.tex + waveMapOffset1;
    output.tex2 = output.position.xyzw;
    
	// Calculate the normal vector against the world matrix only and then normalize the final value.
	output.normal = mul(input.normal, (float3x3)worldMatrix);
	output.normal = normalize(output.normal);

	// Calculate the tangent vector against the world matrix only and then normalize the final value.
	output.tangent = mul(input.tangent, (float3x3)worldMatrix);
	output.tangent = normalize(output.tangent);

	// Calculate the binormal vector against the world matrix only and then normalize the final value.
    output.binormal = mul(input.binormal, (float3x3)worldMatrix);
	output.binormal = normalize(output.binormal);

	return output;
}

float CalcShadowFactor(float4 projTexC)
{
	// Complete projection by doing division by w.
	projTexC.xyz /= projTexC.w;
	
	// Points outside the light volume are in shadow.
	if( projTexC.x < -1.0f || projTexC.x > 1.0f || 
	    projTexC.y < -1.0f || projTexC.y > 1.0f ||
	    projTexC.z < 0.0f )
	    return 0.0f;
	    
	// Transform from NDC space to texture space.
	projTexC.x = +0.5f*projTexC.x + 0.5f;
	projTexC.y = -0.5f*projTexC.y + 0.5f;
	
	// Depth in NDC space.
	float depth = projTexC.z;

	
	// Sample shadow map to get nearest depth to light.
	float s0 = shaderTextures[3].Sample(gShadowSam, projTexC.xy).r;
	float s1 = shaderTextures[3].Sample(gShadowSam, projTexC.xy + float2(SMAP_DX, 0)).r;
	float s2 = shaderTextures[3].Sample(gShadowSam, projTexC.xy + float2(0, SMAP_DX)).r;
	float s3 = shaderTextures[3].Sample(gShadowSam, projTexC.xy + float2(SMAP_DX, SMAP_DX)).r;
	
	// Is the pixel depth <= shadow map value?
	float result0 = depth <= s0 + SHADOW_EPSILON;
	float result1 = depth <= s1 + SHADOW_EPSILON;
	float result2 = depth <= s2 + SHADOW_EPSILON;
	float result3 = depth <= s3 + SHADOW_EPSILON;	
	
	// Transform to texel space.
	float2 texelPos = SMAP_SIZE*projTexC.xy;
	
	// Determine the interpolation amounts.
	float2 t = frac( texelPos );
	
	// Interpolate results.
	return lerp( lerp(result0, result1, t.x), 
	             lerp(result2, result3, t.x), t.y);
}

////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 BumpMapPixelShader(PixelInputType input) : SV_Target
{
	float4 textureColor;
    float4 bumpMap;
    float3 bumpNormal;
    float4 bumpMap1;
    float3 bumpNormal1;
	float3 lightDir;
	float lightIntensity;
	float4 color;
	float3 reflection;

	float4 diffuse;
	float4 ambient;
	float4 specular;
	float4 tex;
	tex = input.tex2;
	// Sample the texture pixel at this location.
	tex.xyz /= tex.w;
	tex.x =  0.5f * tex.x + 0.5f;
	tex.y = -0.5f * tex.y + 0.5f;
	textureColor = shaderTextures[2].Sample(SampleType, tex.xy);
	
    // Sample the pixel in the bump map.
    bumpMap = shaderTextures[1].Sample(SampleType, input.tex);
    bumpMap1 = shaderTextures[0].Sample(SampleType, input.tex1);
    // Expand the range of the normal value from (0, +1) to (-1, +1).
    bumpMap = (bumpMap * 2.0f) - 1.0f;
    bumpMap1 = (bumpMap1 * 2.0f) - 1.0f;
    // Calculate the normal from the data in the bump map.
    bumpNormal = bumpMap.z * input.normal + bumpMap.x * input.tangent + bumpMap.y * input.binormal;
	bumpNormal1 = bumpMap1.z * input.normal + bumpMap1.x * input.tangent + bumpMap1.y * input.binormal;
    // Normalize the resulting bump normal.
    bumpNormal = normalize((bumpNormal + bumpNormal1) * 0.5f);
	float2 fTexOffset = (bumpNormal.xy - 0.5f) * 0.02f;
	
	if(bEnableNoise)
		tex.xy += fTexOffset;
	textureColor = shaderTextures[2].Sample(SampleType, tex.xy);
	// Invert the light direction for calculations.
	lightDir = -lightDirection;

	// Calculate the amount of light on this pixel based on the bump map normal value.
	lightIntensity = saturate(dot(bumpNormal, lightDir));
	
	ambient = ambientColor * ambientMaterial;
	diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
	specular = float4(0.0f, 0.0f, 0.0f, 0.0f);
	
	if(bEnableNormal)
	{
		if(lightIntensity > 0.0f)
		{
			diffuse = diffuseColor * lightIntensity * diffuseMaterial;
			//reflection = normalize(2 * lightIntensity * bumpNormal - lightDir);
			reflection = reflect(lightDirection, bumpNormal); 

			specular = pow(saturate(dot(reflection, input.viewDirection)), specularPower);
			specular = specular * specularColor * specularMaterial;
		}
		diffuse = textureColor * diffuse;		
	}
	else
	{
		diffuse = textureColor;
	}
	
	if(bEnableNoise)
		input.projTexC.xy += fTexOffset;
	float shadowFactor = CalcShadowFactor(input.projTexC);
	if(shadowFactor == 0.0f)
	{
		color = saturate((ambient + diffuse + specular) * ambientMaterial.a);
	}
	else
	{
		color = saturate(ambient + diffuse + specular);
	}
	

	color.a = diffuseMaterial.a;
	
	return color;
}


////////////////////////////////////////////////////////////////////////////////
// Technique
////////////////////////////////////////////////////////////////////////////////
technique10 BumpMapTechnique
{
    pass pass0
    {
		//SetBlendState(AlphaBlendingOn, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xFFFFFFFF);
        SetVertexShader(CompileShader(vs_4_0, BumpMapVertexShader()));
        SetPixelShader(CompileShader(ps_4_0, BumpMapPixelShader()));
        SetGeometryShader(NULL);
    }
}